<template>
  <view class="template-communique tn-safe-area-inset-bottom">
    <!-- 顶部自定义导航 -->
    <view :style="{ height: vuex_custom_bar_height + 'px' }" class="tn-navbg">
      <tn-nav-bar customBack fixed backgroundColor="#ffffff">
        <view slot="back" class="tn-custom-nav-bar__back" @click="goBack">
          <text class="icon tn-icon-left"></text>
          <text class="icon tn-icon-home-capsule-fill"></text>
        </view>
        <view class="tn-flex tn-flex-col-center tn-flex-row-center">
          <text class="tn-text-bold tn-text-xl tn-color-black">
            {{ mode === "create" ? "创建订单" : "修改订单" }}
          </text>
        </view>
      </tn-nav-bar>
    </view>

    <!-- 页面实际内容 -->
    <view class="page">
      <tn-form
        ref="form"
        :model="form"
        :borderBottom="false"
        :errorType="['toast']"
        labelPosition="top"
      >
        <tn-form-item label="选择类目" :borderBottom="false">
          <tn-input
            placeholder="请选择类目"
            rightIcon="down"
            borderColor="#e5e7eb"
            :showRightIcon="true"
            :border="true"
            :disabled="true"
            :value="categorySelectionText"
            @click="showCategorySelectionsSelect = true"
          />
          <tn-select
            v-model="showCategorySelectionsSelect"
            mode="multi-auto"
            borderColor="#e5e7eb"
            :list="categorySelectionList"
            :safeAreaInsetBottom="true"
            @confirm="onChangeCategorySelectionList"
          />
        </tn-form-item>
        <tn-form-item prop="categorySelectionOne" class="hidden">
          <tn-input v-model="form.categorySelectionOne" />
        </tn-form-item>
        <tn-form-item prop="categorySelectionTwo" class="hidden">
          <tn-input v-model="form.categorySelectionTwo" />
        </tn-form-item>

        <tn-form-item prop="title" label="标题" :borderBottom="false">
          <tn-input
            v-model="form.title"
            placeholder="请输入项目标题"
            borderColor="#e5e7eb"
            :border="true"
          />
        </tn-form-item>

        <tn-form-item
          prop="requirementDescription"
          label="需求描述"
          :borderBottom="false"
        >
          <view v-if="showRichTextEditor" class="rich-text-editor">
            <editor
              id="editor"
              class="editor"
              placeholder="请输入需求描述"
              show-img-size
              show-img-toolbar
              show-img-resize
              @ready="onEditorReady"
            ></editor>
          </view>
          <view v-else class="requirement-description-container">
            <view 
              v-if="form.requirementDescription" 
              class="requirement-description-content"
              @click="editRequirementDescription"
            >
              <div class="requirement-text" v-html="sanitizeHtml(form.requirementDescription)"></div>
              <!-- <view class="edit-hint">
                <text class="edit-icon">✏️</text>
                <text class="edit-text">点击编辑</text>
              </view> -->
            </view>
            <tn-input
              v-else
              v-model="form.requirementDescription"
              type="textarea"
              placeholder="请详细描述您的项目需求"
              borderColor="#e5e7eb"
              :border="true"
              :height="256"
              :autoHeight="true"
              :maxLength="Number.MAX_SAFE_INTEGER"
            />
          </view>
          <view style="display: flex; flex-direction: row; margin-top: 13rpx;">
            <tn-button
              padding="0"
              fontColor="#4b7bf5"
              :fontSize="28"
              :plain="true"
              :border="false"
              @click="generateRequirementDescription"
            >
              <image
                style="width: 28rpx; height: 28rpx; margin-right: 7.5rpx;"
                src="/static/images/discovery/ai-requirementDescription.png"
              />
              <text>{{ generating ? '取消生成' : 'AI生成需求描述' }}</text>
            </tn-button>
          </view>
        </tn-form-item>

        <tn-form-item label="追加服务" :borderBottom="false">
          <tn-button
            v-for="service in selectedAdditionalServices"
            :key="service.value"
            shape="round"
            backgroundColor="#F3F4F6"
            @click="onRemoveAdditionalService(service)"
          >
            {{ service.label }}
            <text class="tn-icon-close" style="margin-left: 8rpx;"></text>
          </tn-button>
          <tn-button
            v-show="additionalServicesList.length > 0"
            shape="round"
            backgroundColor="#F3F4F6"
            @click="showAdditionalServicesSelect = true"
          >
            <text class="tn-icon-add"></text>
          </tn-button>
          <tn-select
            v-model="showAdditionalServicesSelect"
            borderColor="#e5e7eb"
            :list="additionalServicesList"
            :safeAreaInsetBottom="true"
            @confirm="onChangeAdditionalServices"
          />
        </tn-form-item>
        <tn-form-item prop="additionalServices" class="hidden">
          <tn-input v-model="form.additionalServices" />
        </tn-form-item>

        <tn-form-item
          prop="expectedDeliveryTime"
          label="期望交付时间"
          :borderBottom="false"
        >
          <tn-input
            v-model="form.expectedDeliveryTime"
            placeholder="请选择时间"
            rightIcon="down"
            borderColor="#e5e7eb"
            :showRightIcon="true"
            :border="true"
            :disabled="true"
            @click="showExpectedDeliveryTimeCalendar = true"
          />
          <tn-calendar
            v-model="showExpectedDeliveryTimeCalendar"
            mode="date"
            :minDate="today"
            :maxDate="lastDayOfYear"
            :safeAreaInsetBottom="true"
            @change="onChangeExpectedDeliveryTime"
          />
        </tn-form-item>

        <tn-form-item prop="totalPrice" label="定价" :borderBottom="false">
          <view class="tn-flex">
            <tn-input
              v-model="form.totalPrice"
              type="number"
              placeholder="根据需求AI定价"
              borderColor="#e5e7eb"
              class="ai-totalPrice-input"
              :border="true"
            />
            <tn-button
              backgroundColor="rgba(75, 123, 245, 0.1)"
              fontColor="#4b7bf5"
              class="ai-totalPrice-button"
              height="100%"
              :fontSize="32"
              :border="false"
              @click="recommendPrice"
            >
              <image src="/static/images/discovery/ai-totalPrice.png" />
              <text>AI推荐</text>
            </tn-button>
          </view>
        </tn-form-item>

        <tn-form-item
          prop="attachmentList"
          label="附件上传"
          :borderBottom="false"
        >
          <uni-file-picker
            ref="attachmentList"
            fileMediatype="file"
            :file-extname="['pdf','doc','docx','xls','xlsx','ppt','pptx']"
            :limit="3"
            v-model="form.attachmentList"
            :auto-upload="false"
            @select="onSelectFile"
            @delete="onDeleteFile"
          >
            <view class="attachmentList">
              <image src="/static/images/discovery/attachmentList.png" />
              <text class="title">点击上传文件</text>
              <text class="tips">
                支持 PDF、Word、Excel 等格式，最多3个文件，单个文件不超过 10M
              </text>
            </view>
          </uni-file-picker>
        </tn-form-item>

        <view v-if="mode === 'create'" class="agreement">
          <tn-checkbox
            v-model="isCheckedAgreement"
            activeColor="#2F54EB"
            @change="onCheckAgreement"
          />
          <view class="agreement-text">
            <text>我已阅读并同意</text>
            <navigator url="/minepages/platformAgreement">
              <text class="redirectText">《平台服务协议》</text>
            </navigator>
          </view>
        </view>

        <tn-button
          size="lg"
          width="100%"
          margin="22rpx 0 0 0"
          backgroundColor="#4B7BF5"
          fontColor="#FFFFFF"
          :fontSize="32"
          @click="onSave"
        >
          {{ mode === "create" ? "发布订单" : "修改订单" }}
        </tn-button>
      </tn-form>
    </view>
  </view>
</template>

<script>
import template_page_mixin from "@/libs/mixin/template_page_mixin";
import { getCategoryTree } from "@/api/dict";
import { createProject, getProject, updateProject } from "@/api/project";
import { uploadToOSS } from "@/api/oss";
import { to } from "@/util/request";
import { generateRequirementsSync, generateRequirementsStream, recommendPriceSync } from "@/api/ai";
import { marked } from 'marked';

const MODES = ["create", "update"];

export default {
  name: "OrderDetails",
  mixins: [template_page_mixin],
  props: {
    mode: {
      type: String,
      default: "update",
      validator: (value) => MODES.includes(value),
    },
  },
  data() {
    return {
      form: {
        id: "",
        categorySelectionOne: "",
        categorySelectionTwo: "",
        title: "",
        requirementDescription: "",
        additionalServices: "",
        expectedDeliveryTime: "",
        totalPrice: "", // 改为空字符串，便于输入框显示
        aiTotalPrice: null, // AI推荐的价格
        attachments: "",
        // 以下是回显字段
        attachmentList: [],
        categorySelectionMap: {},
        additionalServicesMap: {},
      },

      // 选择类目弹窗
      showCategorySelectionsSelect: false,
      categorySelectionList: [],

      // 追加服务弹窗
      showAdditionalServicesSelect: false,
      additionalServicesList: [],

      // 期望交付时间弹窗
      showExpectedDeliveryTimeCalendar: false,

      // 附件 File 对象数组，用于上传到 OSS
      attachmentFileList: [],

      // 是否同意平台服务协议
      isCheckedAgreement: false,
      isReadAgreement: false,

      // 富文本编辑器相关
      showRichTextEditor: false,
      editorCtx: null,
      
      // AI生成需求描述相关
      generating: false,  // 是否正在生成
      streamRequest: null,  // 流式请求控制器
    };
  },
  watch: {
    // 监听附件列表变化，确保只包含符合格式要求的文件
    'form.attachmentList': {
      handler(newVal, oldVal) {
        if (newVal && newVal.length > 0) {
          const allowExt = ['pdf','doc','docx','xls','xlsx','ppt','pptx'];
          const filteredList = newVal.filter(attachment => {
            const name = attachment.name || '';
            const extname = name.indexOf('.') !== -1 ? name.slice(name.lastIndexOf('.') + 1).toLowerCase() : "";
            const isValid = allowExt.includes(extname);
            if (!isValid) {
              console.warn(`watch中过滤掉不符合格式要求的文件: ${name}`);
              // 显示提示
              uni.showToast({
                icon: 'none',
                title: `不支持的文件格式: ${extname}`,
                duration: 2000
              });
            }
            return isValid;
          });
          
          // 如果过滤后的列表与原始列表不同，则更新
          if (filteredList.length !== newVal.length) {
            this.$nextTick(() => {
              this.form.attachmentList = filteredList;
            });
          }
        }
      },
      deep: true,
      immediate: true
    }
  },
  computed: {
    // 类目回显
    categorySelectionText() {
      const values = Object.values(this.form.categorySelectionMap);
      if (values.length === 0) return "";
      return values.join(" - ");
    },
    // 已选择的追加服务（用于渲染可移除的标签）
    selectedAdditionalServices() {
      return Object.keys(this.form.additionalServicesMap).map((value) => ({
        value,
        label: this.form.additionalServicesMap[value],
      }));
    },
    // 期望交付时间 minDate、maxDate
    // 输出格式为 YYYY-MM-DD (例如2025-06-28)
    today() {
      return new Date().toISOString().split("T")[0];
    },
    lastDayOfYear() {
      const year = new Date().getFullYear();
      return `${year}-12-31`;
    }
  },
  methods: {
    // 初始化富文本编辑器
    initEditor() {
      // 创建编辑器上下文对象
      this.editorCtx = uni.createSelectorQuery()
        .select('#editor')
        .context(res => {
          this.editorCtx = res.context;
          // 设置编辑器内容
          if (this.form.requirementDescription) {
            // 尝试将Markdown转换为HTML
            try {
              const html = marked(this.form.requirementDescription);
              this.editorCtx.setContents({
                html: html
              });
            } catch (error) {
              console.error('Markdown转换失败:', error);
              // 如果转换失败，直接设置原始内容
              this.editorCtx.setContents({
                html: this.form.requirementDescription
              });
            }
          }
        })
        .exec();
    },
    
    // 富文本编辑器准备就绪
    onEditorReady() {
      this.initEditor();
    },
    
    // 保存富文本编辑器内容
    saveEditorContent() {
      // 获取编辑器内容
      if (!this.editorCtx) {
        uni.showToast({
          icon: 'error',
          title: '编辑器未初始化'
        });
        return Promise.reject(new Error('编辑器未初始化'));
      }
      
      return new Promise((resolve, reject) => {
        this.editorCtx.getContents({
          success: res => {
            // 将HTML内容保存到表单
            this.form.requirementDescription = res.html;
            resolve(res.html);
          },
          fail: err => {
            console.error('获取编辑器内容失败:', err);
            uni.showToast({
              icon: 'error',
              title: '保存内容失败'
            });
            reject(err);
          }
        });
      });
    },
    
    // 选择类目
    onChangeCategorySelectionList(optionGroup) {
      const [option1, option2, option3] = optionGroup;
      this.form.categorySelectionOne = option1.value;
      this.form.categorySelectionTwo = option2.value;
      this.form.additionalServices = option3?.value ?? "";

      // 处理回显字段
      this.form.categorySelectionMap = {
        [option1.value]: option1.label,
        [option2.value]: option2.label,
      };

      // 处理追加服务及其回显字段
      if (option3) {
        this.additionalServicesList = option2.extra.filter(
          (item) => item.value !== option3.value
        );
        this.form.additionalServicesMap = {
          [option3.value]: option3.label,
        };
      } else {
        this.additionalServicesList = option2.extra;
        this.form.additionalServicesMap = {};
      }
    },
    // 选择追加服务
    onChangeAdditionalServices([option]) {
      if (!option.value) return;

      const additionalServicesArray = [
        ...this.form.additionalServices.split(","),
        option.value,
      ];
      this.form.additionalServices = additionalServicesArray.join(",");
      this.additionalServicesList = this.additionalServicesList.filter(
        (item) => !additionalServicesArray.includes(item.value)
      );

      // 处理回显
      this.$set(this.form.additionalServicesMap, option.value, option.label);
    },
    // 移除已选择的追加服务
    onRemoveAdditionalService(service) {
      const value = service.value;
      // 从映射中删除
      if (this.form.additionalServicesMap[value]) {
        this.$delete(this.form.additionalServicesMap, value);
      }
      // 从逗号字符串中删除该值
      const arr = this.form.additionalServices
        .split(",")
        .filter((v) => v && v !== value);
      this.form.additionalServices = arr.join(",");

      // 把该服务重新放回可选列表（避免重复）
      const exists = this.additionalServicesList.some((i) => i.value === value);
      if (!exists) {
        this.additionalServicesList = [
          { value, label: service.label },
          ...this.additionalServicesList,
        ];
      }
    },
    // 选择期望交付时间
    onChangeExpectedDeliveryTime(dateObject) {
      const { year, month, day, week, isToday, date } = dateObject;
      this.form.expectedDeliveryTime = date;
    },
    // 平台服务协议打勾
    onCheckAgreement({ value, name }) {
      console.log("复选框状态变化:", value, "是否已阅读:", this.isReadAgreement);
      
      // 如果用户取消勾选，重置阅读状态
      if (value === false) {
        this.isCheckedAgreement = false;
        this.isReadAgreement = false; // 重置阅读状态，下次勾选时需要重新阅读
        console.log("取消勾选，重置阅读状态");
        return;
      }
      
      // 如果用户要勾选，但还没有阅读协议，则跳转到协议页面
      if (value === true && !this.isReadAgreement) {
        uni.navigateTo({ url: "/minepages/platformAgreement" });
        // 延迟设置为false，确保UI更新后再重置
        this.$nextTick(() => {
          this.isCheckedAgreement = false;
        });
        return;
      }
      
      // 如果已经阅读过协议，允许直接勾选
      this.isCheckedAgreement = value;
    },
    // 选择附件，将file对象推入列表
    onSelectFile(e) {
      // 支持多选，逐个校验并加入，最多3个
      const allowExt = ['pdf','doc','docx','xls','xlsx','ppt','pptx']
      const maxCount = 3
      const maxSizeBytes = 10 * 1024 * 1024 // 10MB

      if (!Array.isArray(e.tempFiles) || e.tempFiles.length === 0) {
        return
      }

      let addedCount = 0
      for (const file of e.tempFiles) {
        if (this.attachmentFileList.length >= maxCount) {
          break
        }
        const name = file.name || ''
        const ext = name.indexOf('.') !== -1 ? name.slice(name.lastIndexOf('.') + 1).toLowerCase() : ''
        if (!allowExt.includes(ext)) {
          uni.showToast({ icon: 'none', title: `不支持的文件类型: ${ext}` })
          continue
        }
        if (typeof file.size === 'number' && file.size > maxSizeBytes) {
          uni.showToast({ icon: 'none', title: `${name} 超过10MB` })
          continue
        }
        // 去重：避免同名且大小相同的文件重复加入
        const existed = this.attachmentFileList.some(f => (f.name || '') === name && f.size === file.size)
        if (existed) {
          continue
        }
        this.attachmentFileList.push(file)
        addedCount++
      }

      if (this.attachmentFileList.length >= maxCount && addedCount === 0) {
        uni.showToast({ icon: 'none', title: '最多上传3个文件' })
      }
    },
    // 删除附件
    onDeleteFile(e) {
      // 如果是删除已上传的文件，则还需从attachments中移除对应id
      if (e.tempFile.id) {
        this.form.attachments = this.form.attachments.replace(
          e.tempFile.id,
          ""
        );
      }
      this.attachmentFileList.splice(e.index, 1);
    },
    // 提交时上传附件到 OSS
    uploadFile(file) {
      return new Promise(async (resolve, reject) => {
        // 设置固定的上传目录，避免每个文件都创建新目录
        const uploadPath = 'project/attachments';
        const filePath = file.path || file.url // 兼容不同端返回字段
        if (!filePath) {
          reject('文件路径不存在')
          return
        }
        const [err, res] = await to(uploadToOSS(filePath, uploadPath));
        if (err) {
          reject(err.message || '上传失败');
          return;
        }
        resolve(res.data);
      });
    },
    // 提交表单，包括创建和修改
    async onSave() {
      // 创建时必须同意协议
      if (this.mode === "create" && (!this.isReadAgreement || !this.isCheckedAgreement)) {
        uni.showToast({
          icon: "error",
          title: "请先阅读并同意平台服务协议",
        });
        return;
      }

      // 如果正在生成中，先取消生成
      if (this.generating && this.streamRequest) {
        this.streamRequest.abort();
        this.generating = false;
        this.streamRequest = null;
      }

      // 如果当前在富文本编辑器模式，先保存内容
      if (this.showRichTextEditor) {
        try {
          await this.saveEditorContent();
          this.showRichTextEditor = false;
        } catch (error) {
          console.error('保存编辑器内容失败:', error);
          return;
        }
      }

      // 如果有附件，则上传到 OSS，再将结果作为传参
      if (this.attachmentFileList.length > 0) {
        uni.showLoading({ title: '正在上传附件', mask: true })
        const results = await Promise.allSettled(this.attachmentFileList.map(file => this.uploadFile(file)))
        uni.hideLoading()

        const successFiles = results
          .filter(r => r.status === 'fulfilled')
          .map(r => r.value)
        const failedCount = results.length - successFiles.length

        if (successFiles.length === 0) {
          uni.showToast({ icon: 'error', title: '附件全部上传失败' })
          return
        }

        if (failedCount > 0) {
          uni.showToast({ icon: 'none', title: `有${failedCount}个附件上传失败，已忽略` })
        }

        this.form.attachments = [
          ...this.form.attachments.split(","),
          ...successFiles.map((file) => file.id),
        ]
          .filter(Boolean)
          .join(",");
      }

      // 校验字段，随后发送请求
      this.$refs.form.validate(async (isValid) => {
        if (!isValid) return;
        const request = this.mode === "create" ? createProject : updateProject;
        // 过滤掉回显字段，并处理价格字段
        const {
          attachmentList,
          categorySelectionMap,
          additionalServicesMap,
          ...body
        } = this.form;
        
        // 如果用户没有使用AI推荐，则将AI推荐价格设为空
        if (body.aiTotalPrice === null || body.aiTotalPrice === undefined) {
          body.aiTotalPrice = null;
        }
        
        const [err, res] = await to(request(body));
        if (err) {
          uni.showToast({
            icon: "error",
            title: "订单提交失败",
          });
          return;
        }
        uni.showToast({ title: "订单提交成功" });
        setTimeout(() => {
          uni.navigateBack();
        }, 1000);
      });
    },
    // AI生成需求描述
    async generateRequirementDescription() {
      // 检查是否已选择类目和填写标题
      if (!this.form.categorySelectionTwo || !this.form.title) {
        console.log('缺少必要参数：类目或标题');
        uni.showToast({
          icon: "error",
          title: "请先选择类目并填写标题",
        });
        return;
      }
      
      console.log('准备生成需求描述，参数：', {
        projectName: this.form.title,
        categoryCode: this.form.categorySelectionTwo
      });
      
      // 如果正在生成中，则取消请求
      if (this.generating && this.streamRequest) {
        console.log('取消正在进行的请求');
        this.streamRequest.abort();
        this.generating = false;
        this.streamRequest = null;
        
        uni.showToast({
          icon: "none",
          title: "已取消生成",
        });
        return;
      }
      
      // 开始生成
      this.generating = true;
      this.form.requirementDescription = "";
      console.log('已设置生成状态为true，清空需求描述');
      
      uni.showLoading({
        title: "正在生成...",
        mask: true,
      });
      
      // 记录首次收到数据
      let firstChunkReceived = false;
      
      try {
        // 创建流式请求
        this.streamRequest = generateRequirementsStream(
          {
            projectName: this.form.title,
            categoryCode: this.form.categorySelectionTwo,
          },
          // 数据回调
          (content) => {
            if (!content) return;

            console.log('收到数据片段:', content);

            // 处理内容，保持Markdown格式的换行符
            let processedContent = content;

            // 清理多余的空白字符，但保留换行符
            processedContent = processedContent.replace(/[ \t]+/g, ' ');
            
            // 将多个连续换行符规范化为最多两个换行符（段落分隔）
            processedContent = processedContent.replace(/\n{3,}/g, '\n\n');

            // 覆盖之前的内容，而不是拼接
            this.form.requirementDescription = processedContent;

            // 如果是第一次收到数据，隐藏加载提示
            if (!firstChunkReceived) {
              firstChunkReceived = true;
              uni.hideLoading();

              // 显示正在生成的提示
              uni.showToast({
                icon: "none",
                title: "正在生成...",
                duration: 2000
              });
            }
          },
          // 错误回调
          (error) => {
            console.error('生成需求描述出错:', error);
            uni.showToast({
              icon: "error",
              title: "生成失败：" + (error.message || "未知错误"),
            });
            this.generating = false;
            this.streamRequest = null;
          },
          // 完成回调
          () => {
            console.log('流式请求完成');
            if (this.generating) {
              // 正常完成（非取消）
              console.log('生成完成');
              uni.showToast({
                icon: "success",
                title: "生成完成",
              });
            }

            this.generating = false;
            this.streamRequest = null;
          }
        );
      } catch (error) {
        console.error('生成需求描述失败:', error);
        uni.showToast({
          icon: "error",
          title: "生成失败：" + (error.message || "未知错误"),
        });
        this.generating = false;
        this.streamRequest = null;
        uni.hideLoading();
        
        // 如果流式API完全失败，尝试使用同步API
        try {
          uni.showLoading({
            title: "尝试同步请求...",
            mask: true,
          });
          
          const [err, res] = await to(generateRequirementsSync({
            projectName: this.form.title,
            categoryCode: this.form.categorySelectionTwo,
          }));
          
          if (err) {
            throw err;
          }
          
          // 设置结果
          this.form.requirementDescription = res.data;
          
          // 显示富文本编辑器
          this.showRichTextEditor = true;
          
          // 延迟初始化编辑器
          setTimeout(() => {
            this.initEditor();
          }, 100);
          
          uni.hideLoading();
          uni.showToast({
            icon: "success",
            title: "生成成功",
          });
        } catch (syncError) {
          console.error('同步API请求也失败:', syncError);
          uni.hideLoading();
          uni.showToast({
            icon: "error",
            title: "生成失败，请稍后重试",
          });
        }
      }
    },
    // 编辑需求描述
    editRequirementDescription() {
      // 若AI正在生成，先提示用户
      if (this.generating) {
        uni.showModal({
          title: '提示',
          content: 'AI 正在生成需求描述，进入编辑页面将终止生成。是否继续进入？',
          confirmText: '终止并进入',
          cancelText: '继续生成',
          success: (res) => {
            if (res.confirm) {
              // 终止AI生成
              if (this.streamRequest) {
                try { this.streamRequest.abort(); } catch (e) {}
                this.streamRequest = null;
              }
              this.generating = false;
              // 继续进入编辑页
              this._proceedToEdit();
            }
          }
        });
        return;
      }

      // 未在生成，正常进入
      this._proceedToEdit();
    },

    // 进入编辑页前的收尾与跳转
    _proceedToEdit() {
      // 如果当前在富文本编辑器模式，先保存内容
      if (this.showRichTextEditor && this.editorCtx) {
        this.saveEditorContent()
          .then(() => {
            this.navigateToEditPage();
          })
          .catch(() => {
            uni.showToast({
              icon: 'error',
              title: '保存当前内容失败，请重试'
            });
          });
        return;
      }
      // 直接跳转
      this.navigateToEditPage();
    },
    
    // 跳转到编辑页面
    navigateToEditPage() {
      // 处理内容，如果是HTML内容，需要转换为纯文本
      let content = this.form.requirementDescription;
      
      // 如果是HTML内容，尝试提取纯文本
      if (content && content.includes('<')) {
        // 移除HTML标签，保留文本内容
        content = content.replace(/<[^>]*>/g, '');
        // 解码HTML实体
        content = content.replace(/&nbsp;/g, ' ');
        content = content.replace(/&amp;/g, '&');
        content = content.replace(/&lt;/g, '<');
        content = content.replace(/&gt;/g, '>');
        content = content.replace(/&quot;/g, '"');
        // 移除多余的空白字符
        content = content.replace(/\s+/g, ' ').trim();
      }
      
      // 跳转到需求描述编辑页面
      uni.navigateTo({
        url: `/discoverypages/requirementEdit?content=${encodeURIComponent(content)}&id=${this.form.id || ''}&mode=${this.mode}`
      });
    },
    
    // 安全地处理HTML内容，并转换Markdown格式
    sanitizeHtml(html) {
      if (!html) return '';
      
      // 移除可能有害的脚本标签
      let sanitized = html
        .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
        .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
        .replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '')
        .replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, '')
        .replace(/javascript:/gi, '')
        .replace(/on\w+\s*=/gi, '');
      
      // 转换Markdown格式为HTML
      sanitized = this.convertMarkdownToHtml(sanitized);
      
      return sanitized;
    },
    
    // 将Markdown格式转换为HTML
    convertMarkdownToHtml(markdown) {
      if (!markdown) return '';
      
      // 如果内容已经是HTML格式，直接返回
      if (markdown.includes('<') && markdown.includes('>')) {
        return markdown;
      }
      
      let html = markdown;
      
      // 先处理代码块，避免后续处理干扰
      html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
      html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
      
      // 转换分隔线（三个或更多的横线、星号或下划线）
      html = html.replace(/^[-*_]{3,}$/gm, '<hr>');
      
      // 转换标题（从长到短，避免冲突）
      html = html.replace(/^######\s*(.+)$/gm, '<h6>$1</h6>');
      html = html.replace(/^#####\s*(.+)$/gm, '<h5>$1</h5>');
      html = html.replace(/^####\s*(.+)$/gm, '<h4>$1</h4>');
      html = html.replace(/^###\s*(.+)$/gm, '<h3>$1</h3>');
      html = html.replace(/^##\s*(.+)$/gm, '<h2>$1</h2>');
      html = html.replace(/^#\s*(.+)$/gm, '<h1>$1</h1>');
      
      // 转换粗体（避免与斜体冲突，先处理粗体）
      html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
      html = html.replace(/__(.+?)__/g, '<strong>$1</strong>');
      
      // 转换斜体（避免与粗体冲突的更简单方法）
      html = html.replace(/\*([^*\s][^*]*[^*\s])\*/g, '<em>$1</em>');
      html = html.replace(/_([^_\s][^_]*[^_\s])_/g, '<em>$1</em>');
      
      // 处理列表 - 分别处理无序列表和有序列表
      html = this.processLists(html);
      
      // 处理段落 - 将连续的非HTML行包装为段落
      html = this.processParagraphs(html);
      
      return html;
    },
    
    // 处理列表的辅助函数
    processLists(html) {
      const lines = html.split('\n');
      const result = [];
      let inUnorderedList = false;
      let inOrderedList = false;
      
      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const trimmedLine = line.trim();
        
        // 无序列表项
        if (/^-\s+/.test(trimmedLine)) {
          if (!inUnorderedList) {
            if (inOrderedList) {
              result.push('</ol>');
              inOrderedList = false;
            }
            result.push('<ul>');
            inUnorderedList = true;
          }
          const content = trimmedLine.replace(/^-\s+/, '');
          result.push(`<li>${content}</li>`);
        }
        // 有序列表项
        else if (/^\d+\.\s+/.test(trimmedLine)) {
          if (!inOrderedList) {
            if (inUnorderedList) {
              result.push('</ul>');
              inUnorderedList = false;
            }
            result.push('<ol>');
            inOrderedList = true;
          }
          const content = trimmedLine.replace(/^\d+\.\s+/, '');
          result.push(`<li>${content}</li>`);
        }
        // 非列表项
        else {
          if (inUnorderedList) {
            result.push('</ul>');
            inUnorderedList = false;
          }
          if (inOrderedList) {
            result.push('</ol>');
            inOrderedList = false;
          }
          result.push(line);
        }
      }
      
      // 关闭未关闭的列表
      if (inUnorderedList) {
        result.push('</ul>');
      }
      if (inOrderedList) {
        result.push('</ol>');
      }
      
      return result.join('\n');
    },
    
    // 处理段落的辅助函数
    processParagraphs(html) {
      const lines = html.split('\n');
      const result = [];
      let currentParagraph = [];
      
      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const trimmedLine = line.trim();
        
        // 空行或HTML标签行
        if (!trimmedLine || /^<[^>]+>/.test(trimmedLine) || /^<\/[^>]+>$/.test(trimmedLine)) {
          // 如果有积累的段落内容，先输出段落
          if (currentParagraph.length > 0) {
            result.push(`<p>${currentParagraph.join(' ')}</p>`);
            currentParagraph = [];
          }
          // 输出当前行（可能是空行或HTML标签）
          if (trimmedLine) {
            result.push(line);
          }
        }
        // 普通文本行
        else {
          currentParagraph.push(trimmedLine);
        }
      }
      
      // 处理最后的段落
      if (currentParagraph.length > 0) {
        result.push(`<p>${currentParagraph.join(' ')}</p>`);
      }
      
      return result.join('\n');
    },
    
    // AI推荐价格
    async recommendPrice() {
      // 检查必要参数
      if (!this.form.categorySelectionTwo || !this.form.title || !this.form.requirementDescription) {
        uni.showToast({
          icon: "error",
          title: "请先完善类目、标题和需求描述",
        });
        return;
      }

      uni.showLoading({
        title: "正在推荐价格...",
        mask: true,
      });

      try {
        console.log('发起价格推荐请求，参数：', {
          projectName: this.form.title,
          categoryCode: this.form.categorySelectionTwo,
          description: this.form.requirementDescription
        });

        // 直接使用 Promise 而不是 to() 函数，避免异步生成器问题
        const res = await recommendPriceSync({
          projectName: this.form.title,
          categoryCode: this.form.categorySelectionTwo,
          description: this.form.requirementDescription
        });

        uni.hideLoading();

        // 检查返回结果
        if (!res || res.code !== 200) {
          console.error('价格推荐API返回错误:', res);
          uni.showToast({
            icon: "error",
            title: res?.msg || "价格推荐失败",
            duration: 3000
          });
          return;
        }

        // 设置推荐价格
        // 注意：res.data 可能是字符串（甚至被 ```json ... ``` 包裹），需要先解析
        let priceData = res.data;
        if (typeof priceData === 'string') {
          try {
            // 解决手机调试时的乱码问题
            let cleaned = priceData.trim();
            
            // 移除所有可能导致乱码的特殊字符
            cleaned = cleaned.replace(/[^\x00-\x7F]/g, ''); // 只保留ASCII字符
            
            // 处理常见的换行符
            cleaned = cleaned.replace(/↵/g, '\n');
            cleaned = cleaned.replace(/\r\n/g, '\n');
            cleaned = cleaned.replace(/\r/g, '\n');
            
            // 兼容 Markdown 代码块 ```json ... ``` 或 ``` ... ```
            const fenceMatch = cleaned.match(/```(?:json|JSON)?\s*([\s\S]*?)\s*```/);
            if (fenceMatch) {
              cleaned = fenceMatch[1].trim();
            }
            
            console.log('清理后的价格数据:', cleaned);
            
            // 优先尝试 JSON 解析
            try {
              priceData = JSON.parse(cleaned);
            } catch (jsonErr) {
              // 退化处理：从文本中提取第一个数字作为价格
              const numMatch = cleaned.match(/([0-9]+(?:\.[0-9]+)?)/);
              if (numMatch) {
                priceData = { price: Number(numMatch[1]) };
              } else {
                console.error('解析价格数据失败:', jsonErr, cleaned);
                priceData = null;
              }
            }
          } catch (e) {
            console.error('解析价格数据失败:', e);
            priceData = null;
          }
        }
        
        if (res && priceData && priceData.price !== undefined) {
          const recommendedPrice = priceData.price;
          
          
          // 保存AI推荐的原始价格
          this.form.aiTotalPrice = Number(recommendedPrice);
          // 设置用户可编辑的价格（默认与AI推荐价格相同）
          this.form.totalPrice = String(recommendedPrice);
          
          // 强制更新视图
          this.$forceUpdate();
          

          // 延迟显示成功提示，确保数据更新完成
          setTimeout(() => {
            uni.showToast({
              icon: "success",
              title: "价格推荐成功",
            });
          }, 100);
        } else {
          console.error('价格推荐返回数据格式异常:', res);
          uni.showToast({
            icon: "error",
            title: "价格推荐返回数据异常",
          });
        }
      } catch (error) {
        console.error('价格推荐请求异常:', error);
        uni.hideLoading();
        uni.showToast({
          icon: "error",
          title: "网络请求失败：" + (error.message || "未知错误"),
          duration: 3000
        });
      }
    },
    
  },
  async onLoad(option) {
    const { id } = option;

    // 如果从协议页面返回，则应该接收到是否同意协议的消息
    uni.$on("agreementAccepted", (bool) => {
      console.log("收到协议同意事件:", bool);
      this.isReadAgreement = bool;
      this.isCheckedAgreement = bool; // 同时设置复选框为选中状态
    });

    // 如果用户提前返回协议页面，则取消协议勾选状态
    uni.$on("agreementCancelled", (bool) => {
      console.log("收到协议取消事件");
      this.isReadAgreement = false;
      this.isCheckedAgreement = false;
    });

    // 监听需求描述编辑页面返回的数据
    uni.$on("requirementDescriptionUpdated", (content) => {
      if (content && typeof content === 'string') {
        this.form.requirementDescription = content;
        // 如果当前在富文本编辑器模式，需要重新初始化编辑器
        if (this.showRichTextEditor) {
          this.$nextTick(() => {
            this.initEditor();
          });
        }
      }
    });

    // 查询类目字典，并转换为 tn-select 支持的格式
    const fetchCategoryTree = () =>
      new Promise(async (resolve, reject) => {
        const [err, res] = await to(getCategoryTree());
        if (err) {
          reject(err.message);
          return;
        }

        const convertFieldNames = (root) => {
          const children = root.children.length
            ? root.children.map(convertFieldNames)
            : undefined;
          const option = {
            label: root.name,
            value: root.code,
            extra: children || [],
            // 如果该类目没有 children，但设置了 option.children = []，会在 tn-select.vue:251 报错 Cannot read property 'children' of undefined，并且无法渲染选择列表
            // 但如果不设置 option.children，又会在 tn-select.vue:320 报错 Cannot read property '0' of undefined
            // 不管怎样都会报错，暂时无解
            children,
          };
          return option;
        };

        this.categorySelectionList = res.data.map(convertFieldNames);
        resolve();
      });

    // 查询项目详情
    const fetchProject = () =>
      new Promise(async (resolve, reject) => {
        if (!id) {
          resolve();
          return;
        }

        const [err, res] = await to(getProject(id));
        if (err) {
          reject(err.message);
          return;
        }

        // 项目字段映射到表单
        for (const key in this.form) {
          const value = res.data[key];
          if (value === null) continue;
          if (key === "attachmentList") {
            // 附件回显字段，需要转换成 uni-file-picker 支持的格式
            // 同时过滤掉不符合文件格式要求的附件
            const allowExt = ['pdf','doc','docx','xls','xlsx','ppt','pptx'];
            this.form[key] = value
              .map((attachment) => {
                const url = attachment.url || attachment.fileUrl || attachment.path || "";

                // 仅按 fileName 解析
                let name = "";
                const fileNameField = attachment.fileName || "";
                if (fileNameField) {
                  if (fileNameField.indexOf('/') !== -1) {
                    const parts = fileNameField.split('/');
                    const beforeFirstSlash = parts[0];
                    name = beforeFirstSlash && beforeFirstSlash.indexOf('.') !== -1
                      ? beforeFirstSlash
                      : parts[parts.length - 1];
                  } else {
                    name = fileNameField;
                  }
                }
                // 兜底用 url 末段
                if (!name && url) {
                  name = url.substring(url.lastIndexOf('/') + 1);
                }

                // 若为 null/空，则不展示
                if (!name || name.toLowerCase() === 'null') {
                  return null;
                }

                // 检查文件格式是否符合要求
                const extname = name.indexOf('.') !== -1 ? name.slice(name.lastIndexOf('.') + 1).toLowerCase() : "";
                if (!allowExt.includes(extname)) {
                  console.warn(`过滤掉不符合格式要求的文件: ${name}`);
                  return null;
                }

                const size = Number(attachment.fileSize || attachment.size || 0);
                return {
                  ...attachment,
                  name,
                  extname,
                  size,
                  url,
                  path: url,
                };
              })
              .filter(Boolean);
          } else {
            // 常规字段
            this.form[key] = value;
          }
        }
        resolve();
      });

    const [err] = await to(Promise.all([fetchCategoryTree(), fetchProject()]));
    
    // 强制刷新附件列表，确保只显示符合格式要求的文件
    this.$nextTick(() => {
      // 重新设置附件列表，触发组件重新渲染
      this.form.attachmentList = [...this.form.attachmentList];
    });
    if (err) {
      uni.showToast({
        icon: "error",
        title: "初始化页面失败",
      });
    } else {
      // 回显类目与追加服务
      try {
        const code1 = this.form.categorySelectionOne;
        const code2 = this.form.categorySelectionTwo;
        if (code1 && code2) {
          const option1 = this.categorySelectionList.find((o) => o.value === code1);
          const childrenLv2 = option1?.extra || option1?.children || [];
          const option2 = childrenLv2.find((o) => o.value === code2);

          if (option1 && option2) {
            this.form.categorySelectionMap = {
              [option1.value]: option1.label,
              [option2.value]: option2.label,
            };

            // 初始化可选的追加服务列表（来自二级类目的子项）
            const lv3List = option2.extra || option2.children || [];
            this.additionalServicesList = Array.isArray(lv3List) ? [...lv3List] : [];

            // 已选中的追加服务（可能是逗号分隔的编码）
            const selectedValues = (this.form.additionalServices || "")
              .split(",")
              .filter(Boolean);

            if (selectedValues.length > 0) {
              // 映射为 label，用于回显
              const map = {};
              selectedValues.forEach((val) => {
                const found = this.additionalServicesList.find((i) => i.value === val);
                if (found) {
                  map[val] = found.label;
                }
              });
              this.form.additionalServicesMap = map;

              // 过滤掉已选择的，剩余保留在可选列表
              this.additionalServicesList = this.additionalServicesList.filter(
                (i) => !selectedValues.includes(i.value)
              );
            } else {
              this.form.additionalServicesMap = {};
            }
          }
        }
      } catch (e) {
        // 安全兜底，避免因字典结构变化导致页面加载失败
        console.warn("初始化类目和追加服务回显失败", e);
      }
    }
  },
  onShow() {
    // 页面显示时，检查协议状态
    console.log("订单页面显示，当前协议状态：", {
      isReadAgreement: this.isReadAgreement,
      isCheckedAgreement: this.isCheckedAgreement
    });
  },
  onReady() {
    // 再次过滤附件列表，确保只显示符合格式要求的文件
    if (this.form.attachmentList && this.form.attachmentList.length > 0) {
      const allowExt = ['pdf','doc','docx','xls','xlsx','ppt','pptx'];
      this.form.attachmentList = this.form.attachmentList.filter(attachment => {
        const name = attachment.name || '';
        const extname = name.indexOf('.') !== -1 ? name.slice(name.lastIndexOf('.') + 1).toLowerCase() : "";
        const isValid = allowExt.includes(extname);
        if (!isValid) {
          console.warn(`onReady中过滤掉不符合格式要求的文件: ${name}`);
        }
        return isValid;
      });
    }
    
    this.$refs.form.setRules({
      categorySelectionOne: [
        {
          required: true,
          message: "请选择一级类目",
        },
      ],
      categorySelectionTwo: [
        {
          required: true,
          message: "请选择二级类目",
        },
      ],
      title: [
        {
          required: true,
          message: "请输入项目标题",
        },
      ],
      requirementDescription: [
        {
          required: true,
          message: "请输入需求描述",
        },
      ],
      expectedDeliveryTime: [
        {
          required: true,
          message: "请选择期望交付时间",
        },
      ],
      totalPrice: [
        {
          required: true,
          type: "number",
          message: "请输入纯数字定价",
        },
      ],
    });
  },
  
  // 页面卸载时清理资源
  onUnload() {
    // 取消可能存在的流式请求
    if (this.streamRequest) {
      this.streamRequest.abort();
      this.streamRequest = null;
    }
    
    // 取消协议监听
    uni.$off("agreementAccepted");
    uni.$off("agreementCancelled");
    
    // 取消需求描述编辑监听
    uni.$off("requirementDescriptionUpdated");
  },
};
</script>

<style lang="scss" scoped>
.tn-navbg {
  background-color: #ffffff;
  position: relative;
  z-index: 9999;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.15);
  border-bottom: 1rpx solid #e5e7eb;
}

.tn-custom-nav-bar__back {
  width: 100%;
  height: 100%;
  position: relative;
  display: flex;
  justify-content: space-evenly;
  align-items: center;
  box-sizing: border-box;
  background-color: rgba(0, 0, 0, 0.15);
  border-radius: 1000rpx;
  border: 1rpx solid rgba(255, 255, 255, 0.5);
  color: #ffffff;
  font-size: 18px;

  .icon {
    display: block;
    flex: 1;
    margin: auto;
    text-align: center;
  }

  &:before {
    content: " ";
    width: 1rpx;
    height: 110%;
    position: absolute;
    top: 22.5%;
    left: 0;
    right: 0;
    margin: auto;
    transform: scale(0.5);
    transform-origin: 0 0;
    pointer-events: none;
    box-sizing: border-box;
    opacity: 0.7;
    background-color: #ffffff;
  }
}

.page {
  margin: 0rpx 64rpx 30rpx 64rpx;
  padding-top: 20rpx;
  position: relative;
  z-index: 1;
}

.hidden {
  display: none;
}

.button-row {
  display: flex;
  flex-direction: row;
}

.ai-requirementDescription-button {
  image {
    width: 28rpx;
    height: 28rpx;
    margin-right: 7.5rpx;
  }
}

.ai-totalPrice-input {
  flex: 1;
}

.ai-totalPrice-button {
  display: flex;
  align-items: center;
  margin-left: 16rpx;

  image {
    width: 40rpx;
    height: 32rpx;
    margin-right: 8rpx;
  }
}

.rich-text-editor {
  padding: 0;
  border: 2rpx solid #e5e7eb;
  border-radius: 8rpx;
  background-color: #ffffff;
  
  .editor {
    width: 100%;
    height: 400rpx;
    background-color: #ffffff;
    padding: 20rpx;
    box-sizing: border-box;
  }
  
  .editor-toolbar {
    display: flex;
    flex-direction: row;
    justify-content: flex-end;
    padding: 10rpx 20rpx;
    border-top: 1rpx solid #e5e7eb;
    background-color: #f9fafb;
  }
}

.requirement-description-container {
  width: 100%;
}

.requirement-description-content {
  position: relative;
  padding: 20rpx;
  border: 2rpx solid #e5e7eb;
  border-radius: 8rpx;
  background-color: #ffffff;
  cursor: pointer;
  transition: all 0.3s ease;
  
  &:hover {
    border-color: #4b7bf5;
    background-color: #f8fafc;
  }
  
  .requirement-text {
    display: block;
    font-size: 28rpx;
    line-height: 1.6;
    color: #374151;
    word-wrap: break-word;
    white-space: pre-wrap;
    max-height: 400rpx;
    overflow-y: auto;
    
    // HTML内容样式
    :deep(p) {
      margin: 0 0 16rpx 0;
      line-height: 1.6;
    }
    
    :deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
      margin: 24rpx 0 16rpx 0;
      font-weight: 600;
      line-height: 1.4;
      color: #1f2937;
    }
    
    :deep(h1) {
      font-size: 48rpx;
      margin: 32rpx 0 16rpx 0;
    }
    
    :deep(h2) {
      font-size: 40rpx;
      margin: 28rpx 0 12rpx 0;
    }
    
    :deep(h3) {
      font-size: 36rpx;
      margin: 24rpx 0 10rpx 0;
    }
    
    :deep(h4) {
      font-size: 32rpx;
      margin: 20rpx 0 4rpx 0;
    }
    
    :deep(h5) {
      font-size: 30rpx;
      margin: 18rpx 0 2rpx 0;
    }
    
    :deep(h6) {
      font-size: 28rpx;
      margin: 16rpx 0 2rpx 0;
    }
    
    :deep(ul), :deep(ol) {
      margin: 2rpx 0 16rpx 0;
      padding-left: 32rpx;
    }
    
    :deep(li) {
      margin: 8rpx 0;
      line-height: 1.6;
      padding-left: 8rpx;
    }
    
    :deep(strong), :deep(b) {
      font-weight: 600;
    }
    
    :deep(em), :deep(i) {
      font-style: italic;
    }
    
    :deep(code) {
      background-color: #f3f4f6;
      padding: 4rpx 8rpx;
      border-radius: 4rpx;
      font-family: monospace;
      font-size: 24rpx;
    }
    
    :deep(blockquote) {
      border-left: 4rpx solid #e5e7eb;
      padding-left: 16rpx;
      margin: 16rpx 0;
      color: #6b7280;
      font-style: italic;
    }
    
    :deep(hr) {
      border: none;
      border-top: 1rpx solid #e5e7eb;
      margin: 32rpx 0;
      height: 0;
    }
  }
  
  .edit-hint {
    position: absolute;
    top: 10rpx;
    right: 10rpx;
    display: flex;
    align-items: center;
    padding: 8rpx 12rpx;
    background-color: rgba(75, 123, 245, 0.1);
    border-radius: 20rpx;
    opacity: 0.8;
    transition: opacity 0.3s ease;
    
    .edit-icon {
      font-size: 24rpx;
      margin-right: 6rpx;
    }
    
    .edit-text {
      font-size: 22rpx;
      color: #4b7bf5;
    }
  }
  
  &:hover .edit-hint {
    opacity: 1;
  }
  
  // 移动端触摸反馈
  &:active {
    transform: scale(0.98);
    transition: transform 0.1s ease;
  }
}



.attachmentList {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12rpx;
  width: 100%;
  padding: 34rpx;
  border: 2rpx dashed #d1d5db;
  border-radius: 24rpx;

  image {
    width: 60rpx;
    height: 64rpx;
  }
  .title {
    font-size: 28rpx;
    line-height: 40rpx;
    color: #6b7280;
  }
  .tips {
    font-size: 24rpx;
    line-height: 32rpx;
    text-align: center;
    color: #9ca3af;
  }
}

.agreement {
  display: flex;
  align-items: center;
  margin-top: 22rpx;
}

.agreement-text {
  display: flex;
  font-size: 28rpx;
  line-height: 40rpx;
  color: #4b5563;
  transform: translateX(-24rpx);

  .redirectText {
    color: #4b7bf5;
  }
}
</style>